// 实现这个项目的构建任务

/**
 * 根据步骤一步一步进行
 * 1、首先，通过 gulp 引入 src(读写流) 和 dest(写入流) 方法
 * 2、暂时约定输出(写入)目录为 dist
 * 3、创建一个 styles 任务，处理 src 文件夹下的 scss 文件
 * 4、创建一个 scripts 任务，处理 src 文件夹下的 js 文件
 * 5、创建一个 pages 任务，处理 src 文件夹下的 html 文件
 * 6、无论是为了调试任务，还是测试代码，三个任务都不好进行，此时使用 gulp 的组合任务（parallel,series）,
 *    parallel 任务同时进行，series 按照任务组合的排序依次进行，根据不同的任务按需使用
 * 7、将 styles、scripts、pages 三个任务通过 parallel 组合为新任务 compile （因为三者互不影响）
 * 8、此时三者只是单纯的将文件写入，并没有做任何的处理，此时我们需要引入插件处理相对应的文件：
 *      - gulp-sass：处理 scss 文件
 *      - gulp-babel：处理 js 文件（此处需要其他插件的辅助：@babel/core @babel/preset-env）
 *      - gulp-swig：处理 html 文件
 * 9、创建一个 images 任务，处理 src 文件夹下的 图片
 * 10、创建一个 font 任务，处理 src 文件夹下的 字体文件
 * 11、创建一个 extra 任务，处理 src 文件夹下的 额外文件（如 public 目录）
 * 12、字体和额外文件可以直接写入到 输出目录中，但是图片需要额外处理---压缩，需要引入插件 gulp-imagemin，
 *     字体文件下也存在 svg，因此也需要 gulp-imagemin 进行处理，gulp-imagemin 遇到非图片的操作也不会做任何处理
 * 13、为了方便处理，将 images font extra 设置为组合任务，简单取名为 static
 * 14、此时我们引入了很多的插件，一个一个引入非常的麻烦，此时我们想要 gulp 插件可以自动的引入，不需要我们去手动引入，
 *     我们需要插件 gulp-load-plugins 的帮助，他可以自动加载所有的 gulp 插件，返回一个函数，调用之后，返回一个对象，
 *     插件名 gulp- 后面的名称就是对象的属性名，如果有两个以上的单词使用驼峰命名
 * 15、此时我们已经基本完成了 src 目录下文件的写入操作，但是每次调用任务，还需要手动的去删除 dist 目录之类的文件，我们进行优化，
 *     引入插件 del，实现文件清除，并且创建 clean 任务
 * 16、此时我们想开启本地的开发服务器查看页面效果，引入插件 browser-sync 进行配置，创建任务 serve
 *       - 通过插件的 create 方法创建一个开发服务器，取名为 bs
 *       - 通过 bs 的 init 方法初始化开发服务器的配置
 *       - 我们需要在编译完成之后再开启服务器，这里存在先后关系，因此使用 series 创建组合任务 develop
 *       - 此处不处理 静态资源（比如图片、文字之类的文件），是因为按照我们的开发习惯和开发效率的考虑，在开发阶段，没有必要每次都对
 *         静态资源进行处理和压缩，只需要在上线时对这些文件进行处理
 * 17、开发服务器此时已经可以正常工作，我们希望添加浏览器的自动刷新功能，此时我们需要引入 gulp 的 watch 方法，对一些文件进行监听，
 *     从而实现对浏览器的刷新
 *     - watch("路径"，"任务") 自动监视一个文件路径的通配符，根据文件变化去决定是否重新执行编译任务
 * 18、为了实现复用，我们将代码的配置抽离，设置 config 的默认配置，并且约定 pages.config.js 为配置的修改文件
 *     - 通过 process.cwd 获取命令行当前的工作目录
 *     - 为读写流设置 cwd 选项，配置查找文件的基准路径
 *     - 配置 bs.reload 实现浏览器的自动更新
 *     - 将原先写死的路径都通过配置参数改写
 * 19、此时我们去查看输出路径，会发现文件路径的引用还存在问题，引用的路径还是 node_modules 下，我们需要通过 gulp-useref 插件
 *     解决文件引用的问题（比如压缩文件中 node_modules 当中的引用），通过去查找构建注释的方式去实现
 * 20、对文件进行压缩，需要不同的插件：gulp-htmlmin gulp-clean-css gulp-uglify，由于此处需要根据不同的文件类型去做不同的处理，
 *     需要另外一个插件 gulp-if
 * 21、此处在进行文件的读写会出现问题，原因是因为一方面对文件进行了读取，另一方面又对文件进行了写入，解决这个问题的方法也非常简单，
 *     就是改变输出的文件，那此时我们约定一个新的输出目录 temp，用于存放开发阶段的文件，dist 目录用于存放最终上线（build）之后的文件
 *     (因为 dist 目录本就应该存放我们最终的文件)
 * 22、最终，整合需要导出的任务
 */

const { src, dest, parallel, series, watch } = require("gulp");

// const sass = require("gulp-sass");
// const babel = require("gulp-babel");
// const swig = require("gulp-swig");
// const imagemin = require("gulp-imagemin");

const del = require("del");
const browserSync = require('browser-sync');
const loadPlugins = require('gulp-load-plugins');
const plugins = loadPlugins();
const bs = browserSync.create();


// 返回命令行当前的工作目录
const cwd = process.cwd()
let config = {
    // 默认配置
    build: {
        src: 'src',
        dist: 'dist',
        temp: 'temp',
        public: 'public',
        paths: {
            styles: 'assets/styles/*.scss',
            scripts: 'assets/scripts/*.js',
            pages: '*.html',
            images: 'assets/images/**',
            fonts: 'assets/fonts/**'
        },
    },
    data: {
        menus: [
            {
                name: 'Home',
                icon: 'aperture',
                link: 'index.html'
            },
            {
                name: 'Features',
                link: 'features.html'
            },
            {
                name: 'About',
                link: 'about.html'
            },
            {
                name: 'Contact',
                link: '#',
                children: [
                    {
                        name: 'Twitter',
                        link: 'https://twitter.com/w_zce'
                    },
                    {
                        name: 'About',
                        link: 'https://weibo.com/zceme'
                    },
                    {
                        name: 'divider'
                    },
                    {
                        name: 'About',
                        link: 'https://github.com/zce'
                    }
                ]
            }
        ],
        pkg: require('./package.json'),
        date: new Date()
    }
}

// 防止没有配置文件的情况
try {
    const loadConfig = require(`${cwd}/pages.config.js`)
    config = Object.assign({}, config, loadConfig)
} catch (e) { }


// clean 任务，自动清除 dist 下的文件
const clean = () => {
    return del([config.build.dist, config.build.temp])
}


// 样式编译任务
const style = () => {
    // 选项 base 是基准路径，输出时可以保留基准路径（本例为src）后面的文件路径格式
    // cwd 工作目录，查找 src() 的基准路径，默认是当前的项目目录，此处指定为 src
    return src(config.build.paths.styles, { base: config.build.src, cwd: config.build.src })
        .pipe(plugins.sass({ outputStyle: 'expanded' }))
        .pipe(dest(config.build.temp))
        .pipe(bs.reload({ stream: true }))
}

// 脚本编译任务
const script = () => {
    return src(config.build.paths.scripts, { base: config.build.src, cwd: config.build.src })
        // 此处使用 require 加载插件是因为字符串的形式，在实际项目中的依赖（node_modules）中会找不到
        // 使用 require 就可以在工作流模块中加载到
        .pipe(plugins.babel({ presets: [require('@babel/preset-env')] }))
        .pipe(dest(config.build.temp))
        .pipe(bs.reload({ stream: true }))
}

// 页面模板编译
const page = () => {
    // src 下任意目录的 html 文件
    return src(config.build.paths.pages, { base: config.build.src, cwd: config.build.src })
        // caches选项用于清除缓存
        .pipe(plugins.swig({ data: config.data, caches: false }))
        .pipe(dest(config.build.temp))
        .pipe(bs.reload({ stream: true }))
}

// 图片文件压缩
const image = () => {
    // ** 通配下面所有的文件
    return src(config.build.paths.images, { base: config.build.src, cwd: config.build.src })
        .pipe(plugins.imagemin())
        .pipe(dest(config.build.dist))
}

// 字体文件处理
const font = () => {
    // ** 通配下面所有的文件
    return src(config.build.paths.fonts, { base: config.build.src, cwd: config.build.src })
        // 此处只是为了压缩字体文件下的svg
        // 如果格式不支持，imagemin 不会处理
        .pipe(plugins.imagemin())
        .pipe(dest(config.build.dist))
}

// 额外的文件(如 public 下)
const extra = () => {
    return src('**', { base: config.build.public, cwd: config.build.public })
        .pipe(dest(config.build.dist))
}

const serve = () => {
    // watch("src/assets/styles/*.scss", styles)
    // watch("src/assets/scripts/*.js", scripts)
    // watch("src/*.html", pages)

    watch(config.build.paths.styles, { cwd: config.build.src }, style);
    watch(config.build.paths.scripts, { cwd: config.build.src }, script);
    watch(config.build.paths.pages, { cwd: config.build.src }, page);

    // 开发阶段，监视src目录下图片等资源的变化，通过 bs.reload 刷新浏览器
    watch([config.build.paths.images, config.build.paths.fonts], { cwd: config.build.src }, bs.reload);
    watch('**', { cwd: config.build.public }, bs.reload);

    // 初始化开发服务器的配置
    bs.init({
        // 提示
        notify: false,
        // 端口
        port: 2080,
        // 是否自动打开浏览器,默认就是 true
        open: true,
        // 指定什么文件变化后自动更新 (也可以通过 bs.reload 实现)
        // files: "dist/**",
        server: {
            // 根目录，但是数组时，接收到一个请求时，会先去第一个目录去找，找不到的情况下依次往后寻找
            baseDir: [config.build.temp, config.build.src, config.build.public],
            // 路由，优先级高于 baseDir
            routes: {
                '/node_modules': 'node_modules'
            }
        }
    })
}

const useref = () => {
    return src(config.build.paths.pages, { base: config.build.temp, cwd: config.build.temp })
        .pipe(plugins.useref({ searchPath: [config.build.temp, '.'] }))
        // html css js 的压缩工作 (gulp-htmlmin gulp-clean-css gulp-uglify)
        // html 压缩需要额外的参数配置
        // 由于此处需要根据不同的文件类型去做不同的处理，需要另外一个插件 gulp-if
        .pipe(plugins.if(/\.js$/, plugins.uglify()))
        .pipe(plugins.if(/\.css$/, plugins.cleanCss()))
        .pipe(plugins.if(/\.html$/, plugins.htmlmin({
            collapseWhitespace: true,
            minifyCSS: true,
            minifyJS: true
        })))
        // 此处不是 dist 是为了防止一边写入一边读取造成冲突
        // .pipe(dest("release"))
        .pipe(dest(config.build.dist))
}

// 通过组合任务组合三者
// 创建组合任务，组合样式、脚本、页面、图片、字体任务（src 文件目录）
// 开发阶段，为了效率考虑，无需每次都将图片之类的静态资源进行编译，在上线之前编译压缩即可
// const compile = parallel(style, script, page, image, font);
const compile = parallel(style, script, page);

const static = parallel(image, font, extra);

// 上线之前
const build = series(clean, parallel(series(compile, useref), static))

// 开发阶段
const develop = series(compile, serve)

module.exports = {
    develop,
    build,
    clean
}